package scp

import (
	"bufio"
	"errors"
	"io"
	"strconv"
	"strings"
)

type ResponseType = uint8

const (
	Ok      ResponseType = 0
	Warning ResponseType = 1
	Error   ResponseType = 2
)

const buffSize = 1024 * 256

// 远程服务器可能返回的类型:ok, warning 和 error
//
// 警告和错误之间的区别在于远程没有关闭连接，但是，警告可以指示文件传输失败（例如无效的目标目录）
//
// 除了 `Ok` 类型之外的所有响应总是有一条消息（可以是空）
//
// 远程在每个 SCP 命令后发送确认，因为在每个命令后都可能发生故障，发送后应读取和检查响应。
type Response struct {
	Type    ResponseType
	Message string
}

// 从io.Reader读取（假设它是远程的输出）并将其解析为Response
func ParseResponse(reader io.Reader) (Response, error) {
	buffer := make([]uint8, 1)
	_, err := reader.Read(buffer)
	if err != nil {
		return Response{}, err
	}

	response_type := buffer[0]
	message := ""
	if response_type > 0 {
		buffered_reader := bufio.NewReader(reader)
		message, err = buffered_reader.ReadString('\n')
		if err != nil {
			return Response{}, err
		}
	}

	return Response{response_type, message}, nil
}

func (r *Response) IsOk() bool {
	return r.Type == Ok
}

func (r *Response) IsWarning() bool {
	return r.Type == Warning
}

// 当远程响应错误时返回真
func (r *Response) IsError() bool {
	return r.Type == Error
}

// 当远程响应warning或error时返回真
func (r *Response) IsFailure() bool {
	return r.Type > 0
}

// 远程返回的消息
func (r *Response) GetMessage() string {
	return r.Message
}

type FileInfos struct {
	Message     string
	Filename    string
	Permissions string
	Size        int64
}

func (r *Response) ParseFileInfos() (*FileInfos, error) {
	message := strings.ReplaceAll(r.Message, "\n", "")
	parts := strings.Split(message, " ")
	if len(parts) < 3 {
		return nil, errors.New("无法将消息解析为文件信息")
	}

	size, err := strconv.Atoi(parts[1])
	if err != nil {
		return nil, err
	}

	return &FileInfos{
		Message:     r.Message,
		Permissions: parts[0],
		Size:        int64(size),
		Filename:    parts[2],
	}, nil
}

// 向远程写入“Ack”消息，不等待其响应，因此需要单独调用 ParseResponse 以检查确认是否成功
func Ack(writer io.Writer) error {
	var msg = []byte{0}
	n, err := writer.Write(msg)
	if err != nil {
		return err
	}
	if n < len(msg) {
		return errors.New("写入缓冲区失败")
	}
	return nil
}
